﻿// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using Kailua.net.windward.utils;
using Microsoft.Win32;

// bugbug
// 0: get user/system/all tables/views
// 1: get FK:PK relationships for columns.
// 3: unit tests
// 5: WrAccessSyntax - set param, insert get PK
// 5: WrAccessDataReader - is there an Access timestamp type?

namespace net.windward.utils.ado.Access
{
	/// <summary>
	/// This is similiar to DbProviderFactory and provides additional functionality for each provider. All properties to list
	/// if a provider can perform any task in it's child classes is provider here, not in the child classes. This
	/// class can return IWrServer objects for a given server/instance that exists on a computer. The ODBC/OleDb
	/// concept of a Provider (which is different from a DbProviderFactory) is handled as a property set in the
	/// WrProviderFactory class so all other cases do not need to implement a class for that layer.
	/// This is called WrVendor instead of WrProvider because it is different from DbProviderFactory and because
	/// we use the term Provider for the ODBC/OleDb providers.
	/// This is the class for the Access database using it's ODBC driver.
	/// </summary>
	public class WrAccessVendor : WrVendor
	{
		/// <summary>
		/// The DbProviderFactory class for this vendor
		/// </summary>
		public const string FactoryClass = "System.Data.Odbc";

		/// <summary>
		/// The suggested name for this vendor.
		/// </summary>
		public const string FactoryName = "Access (ODBC)";

		private string odbcProvider;

		/// <summary>
		/// Creates an Access vendor object.
		/// </summary>
		public WrAccessVendor()
			: base(FactoryName, "The standard Access connector", FactoryClass)
		{
		}

		/// <summary>
		/// Creates an Access vendor object.
		/// </summary>
		/// <param name="name">The friendly name of the provider.</param>
		/// <param name="description">The description of this provider (optional).</param>
		/// <param name="providerClass">The class of the provider.</param>
		public WrAccessVendor(string name, string description, string providerClass)
			: base(name, description, providerClass)
		{
		}

		#region Properties

		/// <summary>
		/// True if can enumerate running servers from this vendor. This is implemented seperately from
		/// CanCreateDataSourceEnumerator because DB2 and SqlServer implement CreateDataSourceEnumerator differently.
		/// </summary>
		public override bool CanEnumerateServers
		{
			get { return false; }
		}

		/// <summary>
		/// True if can enumerate databases on a server. This is implemented seperately from
		/// CanCreateDataSourceEnumerator because DB2 and SqlServer implement CreateDataSourceEnumerator differently.
		/// </summary>
		public override bool CanEnumerateDatabases
		{
			get { return false; }
		}

		/// <summary>
		/// True if this vendor has the concept of databases in an installed copy of their product. This is false for Oracle (which
		/// does not have the concept of a database) and for databases like Access that are just a file - and the file is the "server."
		/// </summary>
		public override DATABASE_MODE DatabaseMode
		{
			get { return DATABASE_MODE.NEVER; }
		}

		/// <summary>
		/// True if a database supports stored procedures.
		/// </summary>
		public override bool HasStoredProcedures
		{
			get { return false; }
		}

		/// <summary>
		/// True if can launch the administrator for this vendor. This will return true if it can find the admin
		/// program file. In that case the launch could still fail.
		/// </summary>
		public override bool CanLaunchAdministrator
		{
			get { return false; }
		}

		/// <summary>
		/// True if can run DDL scripts.
		/// </summary>
		public override bool CanRunScripts
		{
			get { return false; }
		}

		/// <summary>
		/// True if can use the WindowsIdentity of the caller eliminating the need to pass a username and password.
		/// </summary>
		public override bool CanUseTrustedConnection
		{
			get { return true; }
		}

		/// <summary>
		/// True if you need to select a provider for this vendor.
		/// </summary>
		public override bool HasProviders
		{
			get { return true; }
		}

		/// <summary>
		/// Returns true if this connector is installed on the system. To be true the connector must be in the GAC and must
		/// be listed in machine.config.
		/// </summary>
		public override bool IsInstalled
		{
			get
			{
				try
				{
					// will throw exception if there is a problem
					DbProviderFactories.GetFactory(FactoryClass);
					OdbcDriverInfo[] odi = _GetOdbcProviders();
					return odi.Length > 0;
				}
				catch (Exception)
				{
					return false;
				}
			}
		}

		/// <summary>
		/// The provider factory subtype.
		/// </summary>
		public override string ProviderSubtype
		{
			get { return "Access"; }
		}

		/// <summary>
		/// The name of the (ODBC) providers for this (vendor) provider.
		/// </summary>
		public override string OdbcProvider
		{
			get { return odbcProvider; }
			set
			{
				odbcProvider = value;
				syntax = new WrAccessSyntax(providerFactory, odbcProvider);
			}
		}

		#endregion

		#region WrProvider methods

		/// <summary>
		/// A connection string for a database on a server.
		/// </summary>
		/// <param name="server">The server to access (ex: localhost).</param>
		/// <param name="database">The database to connect to.</param>
		/// <param name="credentials">The user credentials to access the database.</param>
		/// <param name="showPassword">True to include the password, false to add the password as *****.</param>
		/// <returns>The connection string for this database on this server.</returns>
		public override string ConnectionString(string server, string database, WrCredentials credentials, bool showPassword)
		{

			return ConnectionString(providerFactory, odbcProvider, server, credentials, showPassword);
		}

		internal static string ConnectionString(DbProviderFactory providerFactory, string odbcProvider, string server, WrCredentials credentials, bool showPassword)
		{

			if (string.IsNullOrEmpty(odbcProvider) || string.IsNullOrEmpty(server))
				return "";

			DbConnectionStringBuilder connStrBuilder = providerFactory.CreateConnectionStringBuilder();
			if (!string.IsNullOrEmpty(odbcProvider))
				connStrBuilder.Add("Driver", odbcProvider);

			if (!string.IsNullOrEmpty(server))
				connStrBuilder.Add("Dbq", FileUtils.FullPath(server));
			if (!credentials.UseWindowsIdentity)
			{
				if (!string.IsNullOrEmpty(credentials.Username))
					connStrBuilder.Add("Uid", credentials.Username);
				if (!string.IsNullOrEmpty(credentials.Password))
					connStrBuilder.Add("Pwd", showPassword ? credentials.Password : "*****");
			}
			return connStrBuilder.ConnectionString;
		}

		/// <summary>
		/// Returns the parameters from a connection string making the best guess. Will return null for items it could not determine.
		/// </summary>
		/// <param name="connectionString">The connection string to parse.</param>
		/// <returns>The parameters.</returns>
		public override WrConnectionParams ConnectionParams(string connectionString)
		{

			if (string.IsNullOrEmpty(connectionString))
				return new WrConnectionParams();
			Trap.trap();

			DbConnectionStringBuilder builder = providerFactory.CreateConnectionStringBuilder();
			builder.ConnectionString = connectionString;
			string server = null, username = null, password = null;
			if (builder.ContainsKey("dbq"))
				server = (string) builder["dbq"];
			if (builder.ContainsKey("uid"))
				username = (string) builder["uid"];
			if (builder.ContainsKey("pwd"))
				password = (string) builder["pwd"];
			return new WrConnectionParams(server, null, username, password);
		}

		/// <summary>
		/// Return all (ODBC/OleDb) providers for this (vendor) provider.
		/// </summary>
		/// <exception cref="WrDbException">Thrown if this (vendor) providers does not support (ODBC/OleDb) providers.</exception>
		/// <returns>The name of each provider.</returns>
		public override OdbcDriverInfo[] GetOdbcProviders()
		{
			return _GetOdbcProviders();
		}

		private static OdbcDriverInfo[] _GetOdbcProviders()
		{
			List<OdbcDriverInfo> odi = new List<OdbcDriverInfo>(GetProviders("SOFTWARE\\ODBC\\ODBCINST.INI\\ODBC Drivers\\"));
			if (IntPtr.Size == 4)
				odi.AddRange(GetProviders("SOFTWARE\\Wow6432Node\\ODBC\\ODBCINST.INI\\ODBC Drivers\\"));
			odi.Sort();
			for (int index = 0; index < odi.Count - 1; )
			{
				if (odi[index].Name == odi[index + 1].Name)
					odi.RemoveAt(index + 1);
				else
					index++;
			}
			return odi.ToArray();
		}

		private static IEnumerable<OdbcDriverInfo> GetProviders(string key)
		{

			using (RegistryKey keyCLSID = Registry.LocalMachine.OpenSubKey(key, false))
			{
				if (keyCLSID == null)
					return new OdbcDriverInfo[0];

				string[] drivers = keyCLSID.GetValueNames();
				List<OdbcDriverInfo> odi = new List<OdbcDriverInfo>();
				foreach (string drvr in drivers)
					if (drvr.ToLower().IndexOf("*.mdb") != -1)
						odi.Add(new OdbcDriverInfo(drvr, drvr));
				return odi;
			}
		}

		#endregion

		#region DbProvider methods

		/// <summary>
		/// Returns a new instance of the provider's class that implements the WrCommand class.
		/// </summary>
		/// <returns>A new instance of WrCommand.</returns>
		public override WrCommand CreateCommand()
		{
			return new WrAccessCommand(providerFactory, odbcProvider);
		}

		/// <summary>
		/// Create a server object.
		/// </summary>
		/// <param name="server">The name of the server the database is on.</param>
		public override IWrServer CreateServer(string server)
		{
			return new WrAccessServer(providerFactory, odbcProvider, server);
		}

		#endregion

	}
}